/*******************************************************************************
 * Copyright (c) 2007, Geophysical Center RAS
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Geophysical Center RAS nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using VisualEsse.DataEngine.Types;
using DAI.ClientToolkit;
using System.Collections;
using System.Globalization;

namespace VisualEsse.DataEngine
{
    public struct VerticalCoordDescription
    {
        private string display;
        private string localKey;
        private string metadata;
        private string name;

        public VerticalCoordDescription(string display, string localKey, string metadata, string name)
        {
            this.display = display;
            this.localKey = localKey;
            this.metadata = metadata;
            this.name = name;
        }

        public string DisplayString
        {
            get { return display; }
        }

        public string LocalKey
        {
            get { return localKey; }
        }

        public string Metadata
        {
            get { return metadata; }
        }

        public string Name
        {
            get { return name; }
        }

        public override string ToString()
        {
            return DisplayString;
        }

        public static VerticalCoordDescription Empty
        {
            get { return new VerticalCoordDescription("", "", "", ""); }
        }
    }

    public class EsseParameterVerticalCoord
    {
        private VerticalCoordDescription vc;
        private Dictionary<int, EsseParameterDescription> parameters;

        public EsseParameterVerticalCoord(VerticalCoordDescription vc)
        {
            this.vc = vc;
            parameters = new Dictionary<int, EsseParameterDescription>();
        }

        public void AddLevel(int level, EsseParameterDescription parameter)
        {
            parameters.Add(level, parameter);
        }

        public override string ToString()
        {
            return vc.DisplayString;
        }

        public VerticalCoordDescription VerticalCoord
        {
            get { return vc; }
        }

        public int LevelCount
        {
            get { return parameters.Count; }
        }

        public IEnumerable<int> Levels()
        {
            Dictionary<int, EsseParameterDescription>.KeyCollection keys = parameters.Keys;

            foreach (int level in keys)
            {
                yield return level;
            }
        }

        /// <summary>
        /// Returns the esse parameter description for the specified level.
        /// </summary>
        /// <param name="level">Level of the parameter</param>
        /// <returns>Returns the esse parameter description</returns>
        public EsseParameterDescription this[int level]
        {
            get { return parameters[level]; }
        }        
    }

    /// <summary>
    /// EsseMetadataProvider class gets the metadata from the ESSE OGSA DAI activity.
    /// </summary>
    public class EsseMetadataProvider : MetadataProvider, IComplexSelector
    {
        private string homeDir;
        private string display;
        private string href;
        private string dataSource;
        private string version;
        private TemporalGrid timeSeries;
        private GeoUniformGrid2d spatial;

        private Dictionary<string, List<EsseParameterVerticalCoord>> parameters;
        private Dictionary<string, string> hashNameDisplay;
        private Dictionary<string, EsseParameterVerticalCoord> localKeyToParam;

        private EsseMetadataProvider(string homeDirectory)
            : this(homeDirectory, 
            VisualEsse.DataEngine.Properties.Settings.Default.DataServiceUrl,
            VisualEsse.DataEngine.Properties.Settings.Default.ResourceName,
            VisualEsse.DataEngine.Properties.Settings.Default.DataSource)
        {            
        }

        private EsseMetadataProvider(string homeDirectory, string url, string resourceName, string dataSource)
        {
            this.homeDir = homeDirectory;
            parameters = new Dictionary<string, List<EsseParameterVerticalCoord>>(16);
            hashNameDisplay = new Dictionary<string, string>(16);
            localKeyToParam = new Dictionary<string, EsseParameterVerticalCoord>(16);

            XmlElement root;
            try
            {
                root = GetMetadata(url, resourceName, dataSource);

                if (url.StartsWith("http://"))
                {
                    try
                    {
                        string local = Path.Combine(homeDirectory, GetMetadataFileName(url, dataSource));
                        if (!File.Exists(local) ||
                            ((DateTime.Now - File.GetLastWriteTime(local)) > new TimeSpan(24, 0, 0)))
                        {
                            XmlWriter w = XmlWriter.Create(local);                            
                            GetRegimeElement(root).WriteTo(w);
                            w.Close();

                            System.Diagnostics.Debug.WriteLine(string.Format("Local copy of metadata has been refreshed ({0})", local));
                        }
                    }
                    catch (Exception cex)
                    {
                        System.Diagnostics.Debug.WriteLine(string.Format("Error on metadata caching: {0}. Skipping...", cex.Message));
                    }
                }
            }
            catch (Exception ex)
            {
                root = LoadMetadataFromFile(GetMetadataFileName(url, dataSource));
                if (root == null)
                    root = LoadMetadataFromFile("metadata.xml");

                System.Diagnostics.Debug.WriteLine("Remote metadata is not accessible.\nLocal copy is loaded.\n" + ex.Message);
            }
            Load(root);

            GetVirtualParameters();
        }

        private string GetMetadataFileName(string url, string dataSource)
        {
            return string.Format("metadata-{0}.xml", dataSource);
        }

        private XmlElement LoadMetadataFromFile(string file)
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(System.IO.Path.Combine(homeDir, file));
                return doc.DocumentElement;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Local metadata {0} not found [{1}]", file, ex.Message));
                return null;
            }
        }

        private static EsseMetadataProvider defaultInstance = null;
        private static Dictionary<string, EsseMetadataProvider> metadataProviders = new Dictionary<string, EsseMetadataProvider>();

        public static EsseMetadataProvider CreateEsseMetadataProvider(string homeDirectory, string url, string resourceName, string dataSource)
        {
            string key = string.Format("{0}:{1}", url, dataSource);
            EsseMetadataProvider instance;
            if (!metadataProviders.TryGetValue(key, out instance))
            {
                instance = new EsseMetadataProvider(homeDirectory, url, resourceName, dataSource);
                metadataProviders.Add(key, instance);
            }

            return instance;
        }

        public static EsseMetadataProvider CreateEsseMetadataProvider(string homeDirectory)
        {
            if (defaultInstance != null)
                return defaultInstance;

            return defaultInstance = new EsseMetadataProvider(homeDirectory);
        }

        public override TemporalGrid TemporalGrid
        {
            get { return timeSeries; }
        }

        public override GeoUniformGrid2d SpatialGrid
        {
            get { return spatial; }
        }

        public string DisplayString
        {
            get { return display; }
        }

        public string Href
        {
            get { return href; }                
        }

        public string DataSource
        {
            get { return dataSource; }
        }

        public string Version
        {
            get { return version; }
        }

        public override EsseParameterDescription GetDescription(string localKey, int level)
        {
            return localKeyToParam[localKey][level];
        }

        public override EsseParameterDescription GetDescription(string name, string verticalCoordName, int level)
        {
            List<EsseParameterVerticalCoord> list = parameters[name];
            EsseParameterVerticalCoord epvc = list.Find(
                delegate(EsseParameterVerticalCoord item)
                {
                    return item.VerticalCoord.Name == verticalCoordName;
                });
            if (epvc == null)
                throw new ArgumentException("Esse metadata doesn't containts requested parameter " + name + " for " + verticalCoordName + " coordinates!");
            return epvc[level];
        }

        public override bool Exists(EsseParameter parameter)
        {
            if(!localKeyToParam.ContainsKey(parameter.Name))
                return false;
            foreach (int level in localKeyToParam[parameter.Name].Levels())
            {
                if (level == parameter.Level)
                    return true;
            }

            return false;
        }
                
        #region Initialization

        private void GetVirtualParameters()
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(System.IO.Path.Combine(homeDir, "esse.virtual.xml"));
                XmlElement root = doc.DocumentElement;
                if (root.Name != "metadata")
                    throw new ApplicationException("File esse.virtual.xml is not found");

                XmlNodeList composites = root.SelectNodes("compositeParameter");
                foreach (XmlNode cp in composites)
                {
                    try
                    {
                        string pDisplay = GetAttribute(cp, "display");
                        string pLocalKey = GetAttribute(cp, "localKey");
                        string pName = GetAttribute(cp, "name");
                        string pUnits = GetAttribute(cp, "units");

                        XmlVisualizationElement visual = new XmlVisualizationElement(cp["Visualization"]);

                        XmlNodeList xmlComps = cp.SelectNodes("components/parameter");
                        if (xmlComps.Count == 0)
                        {
                            throw new Exception(string.Format("Virtual parameter {0} has no components", pName));
                        }

                        List<List<EsseParameterVerticalCoord>> compDesc = new List<List<EsseParameterVerticalCoord>>(xmlComps.Count);
                        foreach (XmlNode xmlC in xmlComps)
                        {
                            string name = GetAttribute(xmlC, "name");
                            List<EsseParameterVerticalCoord> lvcd = parameters[name];
                            compDesc.Add(lvcd);
                        }
                                                
                        List<EsseParameterVerticalCoord> lbase = compDesc[0];
                        List<EsseParameterVerticalCoord> compositeVca = new List<EsseParameterVerticalCoord>(lbase.Count); 
                        for (int iParam = 0; iParam < lbase.Count; iParam++)
                        {
                            EsseParameterVerticalCoord vc = lbase[iParam];
                            EsseParameterVerticalCoord compositeVC =
                                new EsseParameterVerticalCoord(vc.VerticalCoord);

                            foreach (int level in vc.Levels())
                            {
                                EsseParameterDescription[] components = new EsseParameterDescription[compDesc.Count];
                                for (int iComponent = 0; iComponent < compDesc.Count; iComponent++)
                                {
                                    components[iComponent] = compDesc[iComponent][iParam][level];
                                }

                                EsseParameterDescription epd = new EsseParameterDescription(
                                    new EsseParameter(pLocalKey, level),
                                    pName, pDisplay, pUnits, visual[level], vc.VerticalCoord,
                                    EsseParameterDescription.EsseParameterType.Vector);
                                epd.Components = components;
                                compositeVC.AddLevel(level, epd);
                            }

                            compositeVca.Add(compositeVC);
                        }

                        // TODO: update EsseParameter and remove this:
                        //if (!localKeyToParam.ContainsKey(pLocalKey))
                        //    localKeyToParam.Add(pLocalKey, epvc);

                        parameters.Add(pName, compositeVca);

                        if (!hashNameDisplay.ContainsKey(pName))
                            hashNameDisplay.Add(pName, pDisplay);                        
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine("Virtual parameter skipped: " + ex.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Metadata for virtual data is not available [" + ex.Message + "]");
            }
        }

        static XmlElement GetMetadata()
        {
            return GetMetadata(VisualEsse.DataEngine.Properties.Settings.Default.DataServiceUrl,
                VisualEsse.DataEngine.Properties.Settings.Default.ResourceName,
                VisualEsse.DataEngine.Properties.Settings.Default.DataSource);
        }

        static XmlElement GetMetadata(string url, string resourceName, string source)
        {
            DataService dai = new DataService(url);
            DataServiceResource esse = dai.DataServiceResource(resourceName);
            //Console.WriteLine("Supported activities:\n" + string.Join("\n", esse.Activities));


            // List of activities
            object[] activities = new object[1];

            // Prepare first activity
            object[] ap = new object[2];
            ap[0] = new GetMetaDataActivityTypeOutput("getXmlDataOutput");
            ap[1] = new GetMetaDataActivityTypeValue("source", source);


            activities[0] = new GetMetaDataActivityType("getMetaDataActivity", ap);

            DateTime start = DateTime.Now;
            XmlElement resp = esse.Perform(new PerformType(null, null, activities));

            //resp.WriteTo(new XmlTextWriter("c:\\temp\\metadata-grid4.xml", Encoding.UTF8));
            DateTime stop = DateTime.Now;

            return resp;
        }

        private XmlElement GetRegimeElement(XmlElement root)
        {
            string ns = "http://ogsadai.org.uk/namespaces/2005/10/types";
            XmlNamespaceManager nsmngr = new XmlNamespaceManager(root.OwnerDocument.NameTable);
            nsmngr.AddNamespace("ns", ns);

            // Checking status
            XmlNode request = root.SelectSingleNode("ns:request", nsmngr);
            XmlAttribute attr = request.Attributes["status"];
            string status = (attr == null) ? null : attr.Value;
            if (status == null || status != "COMPLETED")
                throw new ApplicationException("Esse Metadata Document: Request's status is not OK");

            // Reading values
            XmlNode values = root.SelectSingleNode("ns:result[@name=\"getXmlDataOutput\" and @status=\"COMPLETED\"]", nsmngr);
            if (values == null || values.FirstChild == null)
                throw new ApplicationException("Esse Metadata Document: getXmlDataOutput with CDATA section is not found or its status is not COMPLETED");
            string xmlData = values.FirstChild.Value; // CDATA section
            XmlDocument regimeDoc = new XmlDocument();
            regimeDoc.LoadXml(xmlData);

            // Parsing <Regime>-element
            return regimeDoc.DocumentElement;
        }

        private void Load(XmlElement root)
        {
            parameters.Clear();
            hashNameDisplay.Clear();

            // Element either may contain OGSA DAI wrapping or not:
            if (root.Name == "Regime")
            {
                LoadRegime(root);
                return;
            }

            LoadRegime(GetRegimeElement(root));
        }

        private void LoadRegime(XmlElement regime)
        {            
            display = regime.GetAttribute("display");
            href = regime.GetAttribute("href");
            dataSource = regime.GetAttribute("name");
            version = regime.GetAttribute("version");

            XmlNodeList vcNodes = regime.SelectNodes("VerticalCoord");
            foreach (XmlNode vcNode in vcNodes)
            {
                // Parsing VerticalCoord attributes
                VerticalCoordDescription vc = new VerticalCoordDescription(
                    GetAttribute(vcNode, "display"),
                    GetAttribute(vcNode, "localKey"),
                    GetAttribute(vcNode, "metadata"),
                    GetAttribute(vcNode, "name"));

                // Reading levels
                XmlNodeList levelsNodes = vcNode.SelectNodes("Level");
                int[] levels = new int[Math.Max(1, levelsNodes.Count)];
                for (int i = 0; i < levelsNodes.Count; i++)
                {
                    string levelAttrValue = GetAttribute(levelsNodes[i], "level1");
                    try {
                        levels[i] = int.Parse(levelAttrValue,CultureInfo.InvariantCulture);
                    }
                    catch(FormatException exc) {
                        throw new Exception("Unable to parse metadata. Value of level1 attribute " +
                            levelAttrValue.ToString() + " cannot be parsed as integer");
                    }
                }

                if (levelsNodes.Count == 0)
                {
                    levels[0] = 0;
                }

                // Reading parameters
                XmlNodeList paramNodes = vcNode.SelectNodes("Parameter");
                foreach (XmlNode paramNode in paramNodes)
                {
                    string pDisplay = GetAttribute(paramNode, "display");
                    string pLocalKey = GetAttribute(paramNode, "localKey");
                    string pName = GetAttribute(paramNode, "name");
                    string pUnits = GetAttribute(paramNode, "units");

                    XmlVisualizationElement visual = new XmlVisualizationElement(paramNode["Visualization"]);

                    if (!hashNameDisplay.ContainsKey(pName))
                        hashNameDisplay.Add(pName, pDisplay);

                    List<EsseParameterVerticalCoord> vca;
                    if (!parameters.TryGetValue(pName, out vca))
                    {
                        vca = new List<EsseParameterVerticalCoord>();
                        parameters.Add(pName, vca);
                    }

                    // Building parameter for this vertical coord
                    EsseParameterVerticalCoord epvc = new EsseParameterVerticalCoord(vc);
                    for (int i = 0; i < levels.Length; i++)
                    {
                        EsseParameter parameter = new EsseParameter(pLocalKey, levels[i]);
                        PaletteDescription pd = visual[levels[i]];

                        epvc.AddLevel(levels[i],
                            new EsseParameterDescription(parameter, pName, pDisplay, pUnits, pd, vc));
                    }
                    vca.Add(epvc);

                    // TODO: update EsseParameter and remove this:
                    if (!localKeyToParam.ContainsKey(pLocalKey))
                        localKeyToParam.Add(pLocalKey, epvc);
                }
            }

            XmlNode tsNode = regime["TimeSeries"];
            if (tsNode == null)
                throw new ApplicationException("TimeSeries element is not found in the metadata document");
            int timeStep = int.Parse(GetAttribute(tsNode, "timeStep"));
            TimeSpan tstep = new TimeSpan(0, 0, timeStep);
            DateTime dateFrom = ParseDateTimeWithSuffix(GetAttribute(tsNode, "dateFrom"));
            dateFrom = Ceiling(dateFrom, tstep);
            DateTime dateTo = ParseDateTimeWithSuffix(GetAttribute(tsNode, "dateTo"));

            // HACK! We clamp dateTo to one day if dateTo is in future
            // We need this to force weather DB (mashup) to work.
            if (dateTo > DateTime.Now)
                dateTo = Floor(dateTo - new TimeSpan(1,0,0,0), new TimeSpan(1, 0, 0, 0));
            else
                dateTo = Floor(dateTo, tstep);
            timeSeries = new TemporalGrid(dateFrom, dateTo, tstep);


            XmlNode scNode = regime.SelectSingleNode("SpatialCoord/Grid");
            if (scNode == null)
                throw new ApplicationException("SpatialCoord element is not found in the metadata document");

            GeoPoint seed, step;
            GeoRect domain;
            float a, b, c, d;

            XmlNode node = scNode["step"];
            a = float.Parse(GetAttribute(node, "lat"), CultureInfo.InvariantCulture);
            b = float.Parse(GetAttribute(node, "lon"), CultureInfo.InvariantCulture);
            step = new GeoPoint(a, b);

            node = scNode["seed"];
            a = float.Parse(GetAttribute(node, "lat"), CultureInfo.InvariantCulture);
            b = float.Parse(GetAttribute(node, "lon"), CultureInfo.InvariantCulture);
            seed = new GeoPoint(a, b);

            node = scNode["lowerright"];
            a = float.Parse(GetAttribute(node, "lat"), CultureInfo.InvariantCulture);
            b = float.Parse(GetAttribute(node, "lon"), CultureInfo.InvariantCulture);

            node = scNode["upperleft"];
            c = float.Parse(GetAttribute(node, "lat"), CultureInfo.InvariantCulture);
            d = float.Parse(GetAttribute(node, "lon"), CultureInfo.InvariantCulture);
            domain = new GeoRect(d, b, a, c);

            // TODO: seed?
            spatial = new GeoUniformGrid2d(/*seed,*/ step, domain);
        }

        private DateTime Floor(DateTime value, TimeSpan step)
        {
            if (step.Milliseconds != 0)
                throw new ArgumentException("Step value should have no milliseconds"); 
            int stepSeconds = (int)Math.Floor(step.TotalSeconds);
            int valueSeconds = value.Second + 60 * value.Minute + 3600 * value.Hour;
            int roundSeconds = (int)Math.Floor((float)valueSeconds / stepSeconds) * stepSeconds;
            return new DateTime(value.Year, value.Month, value.Day) + TimeSpan.FromSeconds(roundSeconds);            
        }

        private DateTime Ceiling(DateTime value, TimeSpan step)
        {
            if (step.Milliseconds != 0)
                throw new ArgumentException("Step value should have no milliseconds");
            int stepSeconds = (int)Math.Floor(step.TotalSeconds);
            int valueSeconds = value.Second + 60 * value.Minute + 3600 * value.Hour;
            int roundSeconds = (int)Math.Ceiling((float)valueSeconds / stepSeconds) * stepSeconds;
            return new DateTime(value.Year, value.Month, value.Day) + TimeSpan.FromSeconds(roundSeconds);
        }
        
        public static DateTime ParseDateTimeWithSuffix(string dateString)
        {
            return DateTime.Parse(dateString.Substring(0, 19));
            //try
            //{
            //    if (dateString.EndsWith("GMT") || dateString.EndsWith("UTC"))
            //    {
            //        dateString = dateString.Remove(dateString.Length - 3) + "Z";
            //        return DateTime.Parse(dateString, System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.AssumeUniversal);
            //    }
            //    else
            //        return DateTime.Parse(dateString);
            //}
            //catch (Exception exc)
            //{
            //    System.Windows.Forms.MessageBox.Show(dateString + ": " + exc.Message);
            //    throw exc;
            //}
        }

        private string GetAttribute(XmlNode node, string name)
        {
            XmlAttribute a = node.Attributes[name];
            if (a == null)
                return "";
            return a.Value;
        }

        private class XmlVisualizationElement
        {
            private XmlNode rootNode;
            private PaletteDescription rootPalette;

            public XmlVisualizationElement(XmlNode visualizationNode)
            {
                this.rootNode = visualizationNode;

                rootPalette = CreatePaletteDescription(visualizationNode, PaletteDescription.Default, Range.Empty);
            }

            /// <summary>
            /// Returns the PaletteDescription structure for the specified level
            /// </summary>
            public PaletteDescription this[int level]
            {
                get
                {
                    if (rootNode == null)
                    {
                        return rootPalette;
                    }

                    XmlNode levelNode = rootNode.SelectSingleNode("Levels/Level[@level1=\"" + level.ToString() + "\"]");
                    if (levelNode == null)
                        return rootPalette;
                    
                    return CreatePaletteDescription(levelNode, rootPalette, rootPalette.range);   
                }
            }

            private PaletteDescription CreatePaletteDescription(XmlNode node, PaletteDescription defaultPD, Range defaultRange)
            {
                if (node == null)
                {
                    PaletteDescription pd = defaultPD;
                    pd.range = defaultRange;
                    return pd;
                }

                XmlNode paletteNode = node["Palette"];
                XmlNode rangeNode = node["Range"];

                PaletteDescription palette;
                if (paletteNode == null)
                    palette = defaultPD;
                else
                    palette = ParsePalette(paletteNode);                
                
                Range range;
                if (rangeNode == null)
                    range = defaultRange;
                else
                    range = ParseRange(rangeNode);

                palette.range = range;
                if (range.IsEmpty)
                {
                    palette.minmaxType = PaletteDescription.MinMaxType.ByMoment;
                }
                else
                {
                    palette.minmaxType = PaletteDescription.MinMaxType.Metadata;
                }

                return palette;
            }

            private PaletteDescription ParsePalette(XmlNode paletteNode)
            {
                int opacity = 255;
                XmlAttribute a = paletteNode.Attributes["opacity"];
                if (a != null)
                {
                    int i;
                    if (int.TryParse(a.Value, out i))
                        opacity = i;
                }

                string paletteType = paletteNode.InnerText;
                PaletteDescription.PaletteType type = (PaletteDescription.PaletteType)Enum.Parse(typeof(PaletteDescription.PaletteType), paletteType);

                PaletteDescription pd = new PaletteDescription(Range.Empty, type, PaletteDescription.MinMaxType.ByMoment);                
                pd.Opacity = opacity;
                return pd;
            }

            private Range ParseRange(XmlNode rangeNode)
            {
                string min = GetAttribute(rangeNode, "min");
                string max = GetAttribute(rangeNode, "max");

                double dmin = 0, dmax = 0;
                bool b = false;

                if (min != "")
                    b = double.TryParse(min, out dmin);
                if (b && max != "")
                    b = double.TryParse(max, out dmax);

                return b ? new Range(dmin, dmax) : Range.Empty;
            }

            private string GetAttribute(XmlNode node, string name)
            {
                XmlAttribute a = node.Attributes[name];
                if (a == null)
                    return "";
                return a.Value;
            }
        }

        #endregion

        #region IComplexSelector methods

        public class Parameter
        {
            public string Name;
            public string Display;

            public Parameter(string name, string display)
            {
                Name = name;
                Display = display;
            }

            public override string ToString()
            {
                return Display;
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                if (obj == null) return false;
                if (obj is Parameter)
                {
                    return (((Parameter)obj) == this);
                }

                return false;
            }

            public static bool operator ==(Parameter p1, Parameter p2)
            {
                if (((object)p1) == null || ((object)p2) == null)
                    return false;
                return (p1.Name == p2.Name);
            }

            public static bool operator !=(Parameter p1, Parameter p2)
            {
                if (p1 == null || p2 == null)
                    return true;

                return !(p1 == p2);
            }
        }

        int IComplexSelector.Levels
        {
            get { return 3; }
        }

        object[] IComplexSelector.Find(object value)
        {
            EsseParameterDescription parameter = value as EsseParameterDescription;
            object[] path = new object[4];
            path[0] = new Parameter(parameter.Name, parameter.Description);

            List<EsseParameterVerticalCoord> list = parameters[parameter.Name];
            EsseParameterVerticalCoord epvc = list.Find(
                delegate(EsseParameterVerticalCoord item)
                {
                    return item.VerticalCoord.Name == parameter.VerticalCoord.Name;
                });

            path[1] = epvc;
            path[2] = parameter.Level;
            path[3] = parameter;

            return path;
        }

        int IComplexSelector.GetNumberOfValuesAtLevel(int level, object[] values)
        {
            if (level == 0)
            {
                return hashNameDisplay.Count;
            }
            else if (level == 1)
            {
                if (values.Length < 1)
                    throw new ArgumentException("Enumeration of esse parameters: wrong arguments");

                List<EsseParameterVerticalCoord> list = parameters[((Parameter)values[0]).Name];
                return list.Count;
            }
            else if (level == 2)
            {
                if (values.Length < 2)
                    throw new ArgumentException("Enumeration of esse parameters: wrong arguments");

                //List<EsseParameterVerticalCoord> vclist = parameters[values[0] as string];
                EsseParameterVerticalCoord pvc = values[1] as EsseParameterVerticalCoord;
                if (pvc == null)
                    throw new ArgumentException("Enumeration of esse parameters: wrong arguments");

                return pvc.LevelCount;
            }
            else /*if (level == 3)*/
            {
                return 1;
            }
        }

        IEnumerable IComplexSelector.ValuesAtLevel(int level, object[] values)
        {
            if (level == 0)
            {                
                Dictionary<string,string>.KeyCollection ssKeys = hashNameDisplay.Keys;
                List<string> keys = new List<string>(ssKeys);
                keys.Sort(delegate(string a, string b) { return string.Compare(hashNameDisplay[a], hashNameDisplay[b]); });
                foreach (string name in keys)
                {
                    yield return new Parameter(name, hashNameDisplay[name]);
                }
            }
            else if (level == 1)
            {
                if (values.Length < 1)
                    throw new ArgumentException("Enumeration of esse parameters: wrong arguments");

                List<EsseParameterVerticalCoord> list = parameters[((Parameter)values[0]).Name];
                foreach (EsseParameterVerticalCoord epvc in list)
                {
                    yield return epvc;
                }
            }
            else if (level == 2)
            {
                if (values.Length < 2)
                    throw new ArgumentException("Enumeration of esse parameters: wrong arguments");

                //List<EsseParameterVerticalCoord> vclist = parameters[values[0] as string];
                EsseParameterVerticalCoord pvc = values[1] as EsseParameterVerticalCoord;
                if(pvc == null)
                    throw new ArgumentException("Enumeration of esse parameters: wrong arguments");

                foreach (int pLevel in pvc.Levels())
                {
                    yield return pLevel;
                } 
            }
            else if (level == 3)
            {
                if (values.Length < 3)
                    throw new ArgumentException("Enumeration of esse parameters: wrong arguments");

                //List<EsseParameterVerticalCoord> vclist = parameters[values[0] as string];
                EsseParameterVerticalCoord pvc = values[1] as EsseParameterVerticalCoord;
                yield return pvc[(int)values[2]];
            }
        }

        #endregion
    }
}
